// Copyright 2022 Huawei Cloud Computing Technology Co., Ltd.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
//     http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.


#include "CaeHttpsJob.h"
#include "CaeParamStorage.h"
#include <sys/system_properties.h>
#include "../cae_common/CaeCommon.h"
#include "../cae_agent/CaeAgent.h"
#include "../cae_common/CaeJsonUtil.h"
#include "../cae_common/CaeRequestParams.h"
#include "../cae_service/CaeParamStorage.h"

const int HTTPS_CODE_OK = 200;

CaeHttpsJob &CaeHttpsJob::GetInstance()
{
    static CaeHttpsJob instance;
    if (!instance.IsInit()) {
        instance.Init();
    }
    return instance;
}

CaeHttpsJob::CaeHttpsJob()
{
    m_isInit = false;
    pthread_mutex_init(&m_mutexInit, nullptr);
}

CaeHttpsJob::~CaeHttpsJob()
{
    Deinit();
    pthread_mutex_destroy(&m_mutexInit);
}

bool CaeHttpsJob::IsInit() const
{
    return m_isInit;
}

void CaeHttpsJob::InitResponseMem(HttpsJobType httpsType)
{
    HttpsResponse &response = m_responseManager.find(httpsType)->second;
    if (response.buffer != nullptr) {
        int ret = memset_s(response.buffer, BUF_SIZE, '\0', BUF_SIZE);
        if (ret != EOK) {
            LOGE("Failed to Init response buffer.");
        }
    }
    response.size = 0;
    response.httpCode = 0L;
}

bool CaeHttpsJob::Init()
{
    pthread_mutex_lock(&m_mutexInit);
    if (m_isInit) {
        pthread_mutex_unlock(&m_mutexInit);
        return true;
    }
    HttpsResponse responseQueryAesKey = {};
    HttpsResponse responseQueryPublicCrt = {};
    HttpsResponse responseNotifyStart = {};
    HttpsResponse responseNotifyStop = {};
    HttpsResponse responseNotifyHeartbeat = {};

    responseQueryAesKey.buffer = (char *)malloc(BUF_SIZE);
    if (responseQueryAesKey.buffer == nullptr) {
        pthread_mutex_unlock(&m_mutexInit);
        return false;
    }
    memset_s(responseQueryAesKey.buffer, BUF_SIZE, '\0', BUF_SIZE);

    responseQueryPublicCrt.buffer = (char *)malloc(BUF_SIZE);
    if (responseQueryPublicCrt.buffer == nullptr) {
        pthread_mutex_unlock(&m_mutexInit);
        return false;
    }
    memset_s(responseQueryPublicCrt.buffer, BUF_SIZE, '\0', BUF_SIZE);
    
    responseNotifyStart.buffer = (char *)malloc(BUF_SIZE);
    if (responseNotifyStart.buffer == nullptr) {
        pthread_mutex_unlock(&m_mutexInit);
        return false;
    }
    memset_s(responseNotifyStart.buffer, BUF_SIZE, '\0', BUF_SIZE);

    responseNotifyStop.buffer = (char *)malloc(BUF_SIZE);
    if (responseNotifyStop.buffer == nullptr) {
        pthread_mutex_unlock(&m_mutexInit);
        return false;
    }
    memset_s(responseNotifyStop.buffer, BUF_SIZE, '\0', BUF_SIZE);

    responseNotifyHeartbeat.buffer = (char *)malloc(BUF_SIZE);
    if (responseNotifyHeartbeat.buffer == nullptr) {
        pthread_mutex_unlock(&m_mutexInit);
        return false;
    }
    memset_s(responseNotifyHeartbeat.buffer, BUF_SIZE, '\0', BUF_SIZE);

    AddResponse(HttpsJobType::NOTIFY_STOP, responseNotifyStop);
    AddResponse(HttpsJobType::NOTIFY_START, responseNotifyStart);
    AddResponse(HttpsJobType::NOTIFY_HEARTBEAT, responseNotifyHeartbeat);
    AddResponse(HttpsJobType::QUERY_AES_KEY, responseQueryAesKey);
    AddResponse(HttpsJobType::QUERY_PUBLIC_CRT, responseQueryPublicCrt);

    m_isInit = true;
    pthread_mutex_unlock(&m_mutexInit);
    return true;
}

inline void CaeHttpsJob::AddResponse(HttpsJobType type, HttpsResponse &response)
{
    m_responseManager.insert(make_pair(type, response));
}

void CaeHttpsJob::Deinit() noexcept
{
    auto it = m_responseManager.begin();
    for (; it != m_responseManager.end(); ++it) {
        if (nullptr == it->second.buffer) {
            free(it->second.buffer);
            it->second.buffer = nullptr;
        }
        it->second.size = 0;
        it->second.httpCode = 0L;
    }
    m_isInit = false;
}

bool CaeHttpsJob::QueryAESKey(std::shared_ptr<CaeParamStorage> paramStorage)
{
    std::string queryUrl = CaeConfigManage::GetSignCheckUrl();
    if (queryUrl.empty()) {
        LOGE("Query url is empty.");
        return false;
    }

    auto it = m_responseManager.find(HttpsJobType::QUERY_AES_KEY);
    if (it == m_responseManager.end()) {
        LOGE("Response buffer can't use.");
        return false;
    }
    HttpsResponse &responseQueryAesKey = it->second;

    if (responseQueryAesKey.buffer == nullptr) {
        LOGE("Response queryAesKey is null.");
        return false;
    }

    string ticket = paramStorage->GetTicket();
    if (ticket.empty()) {
        LOGE("Ticket is null.");
        return false;
    }
    string ts = paramStorage->GetAuthTs();
    if (ts.empty()) {
        LOGE("Auth ts is null.");
        return false;
    }
    string sessionId = paramStorage->GetSessionId();
    if (sessionId.empty()) {
        LOGE("Session id is null.");
        return false;
    }
    string encryptedData = paramStorage->GetEncyptedData();
    if (encryptedData.empty()) {
        LOGE("Encrypted data is null.");
        return false;
    }
    string aesIv = paramStorage->GetAesIv();
    if (aesIv.empty()) {
        LOGE("Aes iv is null.");
        return false;
    }
    std::string certs4SignVerify;
    char *bodyInfo;

    cJSON *bodyJson = cJSON_CreateObject();
    if (nullptr == bodyJson) {
        LOGE("Create bodyJson failed.");
        return false;
    }

    cJSON_AddItemToObject(bodyJson, "ticket", cJSON_CreateString(ticket.c_str()));
    cJSON_AddItemToObject(bodyJson, "session_id", cJSON_CreateString(sessionId.c_str()));
    cJSON_AddItemToObject(bodyJson, "auth_ts", cJSON_CreateString(ts.c_str()));

    cJSON_AddItemToObject(bodyJson, "encrypted_data", cJSON_CreateString(encryptedData.c_str()));
    cJSON_AddItemToObject(bodyJson, "aes_iv", cJSON_CreateString(aesIv.c_str()));
    bodyInfo = cJSON_PrintUnformatted(bodyJson);
    cJSON_Delete(bodyJson);
    if (bodyInfo == nullptr) {
        LOGE("BodyStr is null");
        return false;
    }
    string bodyInfoStr = string(bodyInfo);
    free(bodyInfo);
    if (bodyInfoStr.empty()) {
        LOGE("BodyInfoStr is empty.");
        return false;
    }
    auto request = new (std::nothrow) CaeRequestParams("POST", queryUrl, "", bodyInfoStr, true);
    if (nullptr == request) {
        LOGE("Create request failed.");
        return false;
    }

    time_t validDateTime = time(nullptr) + 20;
    bool ret = CaeAgent::GetInstance().m_caeHttps->HttpsPost(request, responseQueryAesKey, validDateTime);
    delete request;
    return ret;
}


bool CaeHttpsJob::QueryPublicCrt(std::shared_ptr<CaeParamStorage> paramStorage)
{
    std::vector<std::string> regionList;
    if (paramStorage->GetClientMode() == CLIENT_MODE_MANAGEMENT) {
        regionList = {CLIENT_MODE_MANAGEMENT};
    } else {
        regionList = {"cn-south-1", "cn-north-4", "cn-east-3",
                      "cn-southwest-2", "cn-east-2",
                      "ap-southeast-1", "ap-southeast-3", "ru-northwest-2"};
        std::string currentRegion = paramStorage->GetRegionId();
        auto it = std::find(regionList.begin(), regionList.end(), currentRegion);
        if (it != regionList.end()) {
            std::rotate(regionList.begin(), it, it + 1);
        }
    }

    auto index = m_responseManager.find(HttpsJobType::QUERY_PUBLIC_CRT);
    if (index == m_responseManager.end()) {
        LOGE("Response buffer can't use.");
        return false;
    }
    HttpsResponse &responseQueryPublicCrt = index->second;

    for (const auto &str : regionList) {
        string url = paramStorage->GetObtainPublicCrtUrl(str);
        if (url.empty()) {
            LOGE("Obtain public crt url is invalid, Send query_PublicKey job failed.");
            continue;
        }
        string filePath = paramStorage->GetPublicCrtFilePath();
        if (filePath.empty()) {
            LOGE("Public crt file path is invalid.");
            break;
        }
        LOGI("url = %s", url.c_str());

        if (responseQueryPublicCrt.buffer == nullptr) {
            LOGE("Response queryPublicCrt is null.");
            continue;
        }

        auto request = new (std::nothrow) CaeRequestParams("", url, "", "", true);
        if (nullptr == request) {
            LOGE("Create request failed.");
            continue;
        }

        bool ret = CaeAgent::GetInstance().m_caeHttps->HttpsDownload(request, filePath);
        delete request;

        if (ret) {
            responseQueryPublicCrt.httpCode = HTTPS_CODE_OK;
            size_t realSize = filePath.size();
            if (realSize > BUF_SIZE) {
                realSize = BUF_SIZE;
            }
            if (memcpy_s(responseQueryPublicCrt.buffer, realSize, filePath.c_str(), realSize) != EOK) {
                LOGE("Failed to copy memory.");
                return false;
            }
            responseQueryPublicCrt.size = static_cast<int>(realSize);
            return ret;
        }
    }

    responseQueryPublicCrt.httpCode = 400;
    string error = "Failed to get public key";
    size_t realSize = error.size();
    if (realSize > BUF_SIZE) {
        realSize = BUF_SIZE;
    }
    if (memcpy_s(responseQueryPublicCrt.buffer, realSize, error.c_str(), realSize) != EOK) {
        LOGE("Failed to copy memory.");
        return false;
    }
    responseQueryPublicCrt.size = static_cast<int>(realSize);
    return false;

}

bool CaeHttpsJob::StartNotify(std::shared_ptr<CaeParamStorage> paramStorage)
{
    string reportUrl = CaeConfigManage::GetEventNotifyUrl();
    if (reportUrl.empty()) {
        LOGE("Report Start event failed, url is empty.");
        return false;
    }

    auto it = m_responseManager.find(HttpsJobType::NOTIFY_START);
    if (it == m_responseManager.end()) {
        LOGE("Response buffer can't use.");
        return false;
    }
    HttpsResponse &responseNotifyStart = it->second;

    if (responseNotifyStart.buffer == nullptr) {
        LOGE("NotifyStart's response is null.");
        return false;
    }

    cJSON *postMsgJson = cJSON_CreateObject();
    if (nullptr == postMsgJson) {
        LOGE("Create postMsgJson failed.");
        return false;
    }
    cJSON *eventJson = cJSON_CreateObject();
    if (nullptr == eventJson) {
        LOGE("Create eventJson failed.");
        cJSON_Delete(postMsgJson);
        return false;
    }

    string currentUTCTime;
    string phoneId = GetPhoneId();
    currentUTCTime = CaeCommon::ToUTCTime(paramStorage->GetStartTime());
    cJSON_AddItemToObject(eventJson, "phone_id", cJSON_CreateString(phoneId.c_str()));
    cJSON_AddItemToObject(eventJson, "status", cJSON_CreateNumber(0));
    cJSON_AddItemToObject(eventJson, "session_id",
        cJSON_CreateString((paramStorage->GetSessionId()).c_str()));
    cJSON_AddItemToObject(eventJson, "ticket",
        cJSON_CreateString((paramStorage->GetTicket()).c_str()));
    cJSON_AddItemToObject(eventJson, "time", cJSON_CreateString(currentUTCTime.c_str())); // 单位:s
    cJSON_AddItemToObject(eventJson, "error_msg", cJSON_CreateString(""));
    cJSON_AddItemToObject(postMsgJson, "event_type", cJSON_CreateString("app"));
    cJSON_AddItemToObject(postMsgJson, "event", eventJson);
    char *updateStartInfo = cJSON_PrintUnformatted(postMsgJson);
    cJSON_Delete(postMsgJson);

    if (updateStartInfo == nullptr) {
        LOGE("Update Start info is null.");
        return false;
    }

    auto updateInfoStr = string(updateStartInfo);
    free(updateStartInfo);
    if (updateInfoStr.empty()) {
        LOGE("Update info string is empty.");
        return false;
    }
    LOGD("Sent update Start info to access-gateway: %s.", updateInfoStr.c_str());

    auto request = new (std::nothrow) CaeRequestParams("POST", reportUrl, "", updateInfoStr, true);
    if (nullptr == request) {
        LOGE("Create request failed.");
        return false;
    }
    auto validDateTime = time(nullptr) + 20;
    if (!CaeAgent::GetInstance().m_caeHttps->HttpsPost(request, responseNotifyStart, validDateTime)) {
        LOGE("Send request_StartNotify failed.");
        delete request;
        return false;
    }
    delete request;
    if (responseNotifyStart.size <= 0) {
        LOGE("Response_notifyStart's size is invalid: %d .", responseNotifyStart.size);
        return false;
    }
    bool ret = ParseHttpsResponse(responseNotifyStart, HttpsJobType::NOTIFY_START);
    return ret;
}

bool CaeHttpsJob::StopNotify(std::shared_ptr<CaeParamStorage> paramStorage)
{
    string reportUrl = CaeConfigManage::GetEventNotifyUrl();
    if (reportUrl.empty()) {
        LOGE("Report Stop event failed, report url is empty.");
        return false;
    }

    auto it = m_responseManager.find(HttpsJobType::NOTIFY_STOP);
    if (it == m_responseManager.end()) {
        LOGE("Response_buffer can't use.");
        return false;
    }
    HttpsResponse &responseNotifyStop = it->second;

    if (responseNotifyStop.buffer == nullptr) {
        LOGE("Response_notifyStop is null.");
        return false;
    }

    cJSON *postMsgJson = cJSON_CreateObject();
    if (postMsgJson == nullptr) {
        LOGE("Create postMsgJson failed.");
        return false;
    }
    cJSON *eventJson = cJSON_CreateObject();
    if (eventJson == nullptr) {
        LOGE("Create eventJson failed.");
        cJSON_Delete(postMsgJson);
        return false;
    }

    string currentUTCTime;
    time_t stopTimestamp = time(nullptr);
    currentUTCTime = CaeCommon::ToUTCTime(stopTimestamp);
    time_t start = paramStorage->GetStartTime();
    time_t runTimes;
    if (start > 0) {
        runTimes = stopTimestamp - start;
    } else {
        runTimes = 0;
    }
    string phoneId = GetPhoneId();
    cJSON_AddItemToObject(eventJson, "phone_id", cJSON_CreateString(phoneId.c_str()));
    cJSON_AddItemToObject(eventJson, "status", cJSON_CreateNumber(1));
    cJSON_AddItemToObject(eventJson, "session_id",
        cJSON_CreateString((paramStorage->GetSessionId()).c_str()));
    cJSON_AddItemToObject(eventJson, "ticket",
        cJSON_CreateString((paramStorage->GetTicket()).c_str()));
    cJSON_AddItemToObject(eventJson, "time", cJSON_CreateString(currentUTCTime.c_str()));
    cJSON_AddItemToObject(eventJson, "run_time", cJSON_CreateNumber(static_cast<double>(runTimes)));
    cJSON_AddItemToObject(eventJson, "error_msg", cJSON_CreateString(""));
    cJSON_AddItemToObject(postMsgJson, "event_type", cJSON_CreateString("app"));
    cJSON_AddItemToObject(postMsgJson, "event", eventJson);
    char *updateOverInfo = cJSON_PrintUnformatted(postMsgJson);
    cJSON_Delete(postMsgJson);

    if (updateOverInfo == nullptr) {
        LOGE("UpdateOverInfo is null.");
        return false;
    }
    string updateInfoStr = string(updateOverInfo);
    free(updateOverInfo);
    if (updateInfoStr.empty()) {
        LOGE("UpdateInfoStr is empty.");
        return false;
    }
    LOGD("Sent update Stop info to access-gateway: %s.", updateInfoStr.c_str());

    CaeRequestParams *request = nullptr;
    request = new (std::nothrow) CaeRequestParams("POST", reportUrl, "", updateInfoStr, true);
    if (request == nullptr) {
        LOGE("Create request failed.");
        return false;
    }
    time_t validDateTime = time(nullptr) + 20;
    if (!CaeAgent::GetInstance().m_caeHttps->HttpsPost(request, responseNotifyStop, validDateTime)) {
        LOGE("Send request_StopNotify failed.");
        delete request;
        return false;
    }
    delete request;

    if (responseNotifyStop.size <= 0) {
        LOGE("Response_notifyStop's size is invalid: %d.", responseNotifyStop.size);
        return false;
    }
    bool ret = ParseHttpsResponse(responseNotifyStop, HttpsJobType::NOTIFY_STOP);
    return ret;
}

bool CaeHttpsJob::HeartbeatNotify(std::shared_ptr<CaeParamStorage> paramStorage)
{
    string heartbeatUrl = CaeConfigManage::GetHeartbeatUrl();
    if (heartbeatUrl.empty()) {
        LOGE("Report heartbeat event failed, report_heartbeat url is empty.");
        return false;
    }

    auto it = m_responseManager.find(HttpsJobType::NOTIFY_HEARTBEAT);
    if (it == m_responseManager.end()) {
        LOGE("HeartbeatNotify response_buffer can't use.");
        return false;
    }
    HttpsResponse &responseNotifyHeartbeat = it->second;

    if (responseNotifyHeartbeat.buffer == nullptr) {
        LOGE("Response_notifyHeartbeat buffer is null.");
        return false;
    }

    string sessionId = "";
    if (paramStorage != nullptr) {
        sessionId = paramStorage->GetSessionId();
    }

    cJSON *postMsgJson = cJSON_CreateObject();
    if (postMsgJson == nullptr) {
        LOGE("Create postMsgJson failed.");
        return false;
    }
    cJSON *eventJson = cJSON_CreateObject();
    if (eventJson == nullptr) {
        LOGE("Create eventJson failed.");
        cJSON_Delete(postMsgJson);
        return false;
    }

    string currentTime;
    time_t now;
    time(&now);
    currentTime = CaeCommon::ToUTCTime(now);
    cJSON_AddItemToObject(eventJson, "phone_id", cJSON_CreateString(GetPhoneId().c_str()));
    cJSON_AddItemToObject(eventJson, "time", cJSON_CreateString(currentTime.c_str()));

    cJSON_AddItemToObject(eventJson, "session_id", cJSON_CreateString(sessionId.c_str()));

    cJSON_AddItemToObject(postMsgJson, "event_type", cJSON_CreateString("heartbeat"));
    cJSON_AddItemToObject(postMsgJson, "event", eventJson);
    char *notifyInfo = cJSON_PrintUnformatted(postMsgJson);
    cJSON_Delete(postMsgJson);

    if (notifyInfo == nullptr) {
        LOGE("NotifyInfo is null.");
        return false;
    }

    auto notifyInfoStr = string(notifyInfo);
    LOGD("HeartBeat info: %s.", notifyInfoStr.c_str());
    free(notifyInfo);
    if (notifyInfoStr.empty()) {
        LOGE("NotifyInfoStr is empty.");
        return false;
    }

    auto request = new (std::nothrow) CaeRequestParams("POST", heartbeatUrl, "", notifyInfoStr, true);
    if (request == nullptr) {
        LOGE("Create request error.");
        return false;
    }
    time_t validDateTime = time(nullptr) + 20;
    if (!CaeAgent::GetInstance().m_caeHttps->HttpsPost(request, responseNotifyHeartbeat, validDateTime)) {
        LOGE("Send heartbeat failed.");
        delete request;
        return false;
    }
    delete request;

    if (responseNotifyHeartbeat.size <= 0) {
        LOGE("Response_notifyHeartbeat's size is invalid: %d.", responseNotifyHeartbeat.size);
        return false;
    }
    bool ret = ParseHttpsResponse(responseNotifyHeartbeat, HttpsJobType::NOTIFY_HEARTBEAT);
    return ret;
}

void CaeHttpsJob::SetPhoneId()
{
    m_phoneId = CaeConfigManage::GetPhoneId();
}

string CaeHttpsJob::GetPhoneId()
{
    return this->m_phoneId;
}

HttpsResponse &CaeHttpsJob::GetResponse(HttpsJobType type)
{
    return m_responseManager.find(type)->second;
}

bool CaeHttpsJob::ContainResponse(HttpsJobType type)
{
    auto it = m_responseManager.find(type);
    if (it != m_responseManager.end()) {
        return true;
    } else {
        return false;
    }
}

bool CaeHttpsJob::ParseHttpsResponse(HttpsResponse &responseInfo, HttpsJobType httpsType)
{
    bool ret = true;
    if (responseInfo.httpCode != HTTPS_CODE_OK) {
        LOGE("Request failed, http code is %ld, responseInfo is %s.", responseInfo.httpCode,
            string(responseInfo.buffer, responseInfo.size).c_str());
        return false;
    }
#if PARSE_HTTPS_RESPONSE
    string response = string(responseInfo.buffer, responseInfo.size);
    cJSON *root = cJSON_Parse(response.c_str());
    if (root == nullptr) {
        LOGE("Response is %s. cJSON_Parse failed: %s",
            string(responseInfo.buffer, responseInfo.size).c_str(), cJSON_GetErrorPtr());
        return false;
    }
    auto request_id = CaeJsonUtil::GetJSONString(root, "request_id");

    switch (httpsType) {
        case HttpsJobType::QUERY_AES_KEY: {
            auto returnCode = CaeJsonUtil::GetJSONString(root, "return_code");
            if (returnCode.empty()) {
                LOGE("Query_AesKey failed, No retuen code.");
                ret = false;
                break;
            }
            if (atoi(returnCode.c_str()) != 0) {
                auto errorMsg = CaeJsonUtil::GetJSONString(root, "error_msg");
                LOGE("Query_AesKey failed, return_code is %s. error_msg is %s. request_id is %s.",
                     returnCode.c_str(), errorMsg.c_str(), request_id.c_str());
                ret = false;
                break;
            }
            break;
        }
        case HttpsJobType::NOTIFY_START: {
            LOGI("Request id is %s.", request_id.c_str());
            break;
        }
        case HttpsJobType::NOTIFY_STOP: {
            LOGI("Request id is %s.", request_id.c_str());
            break;
        }
        default: {
            LOGE("Invalid HttpsJobType.");
            break;
        }
    }
    cJSON_Delete(root);
#endif
    return ret;
}
